#
# Copyright (c) 2007, Blair Kitchen.
#
# See the file "license.terms" for information on usage and redistribution of
# this file, and for a DISCLAIMER OF ALL WARRANTIES.
#

#
# Package for parsing SVN dump files
#

# Package dependencies
package require Tcl 8.4
package require dict 8.5

# Package information
package provide svndump 0.1

# Create the default namespace
namespace eval ::svndump {}

#
# Procedure to parse the specified channel.  Callbacks are used to process
# the data.  The cmd argument contains a command called to process each item.
# At least one argument is passed to the command: the type of callback.  Additional
# arguments may be passed as specified below.
#
# Callbacks Types:
#
#  beginParsing
#  endParsing
#
#  beginRevision revisionNumber
#  endRevision
#
#  processHeaders dict
#
#  beginNode path
#  endNode
#
#  beginPropertyContent propertyName
#  processPropertyContent data
#  endPropertyContent
#
#  beginTextContent
#  processTextContent data
#  endTextContent
#
proc ::svndump::parse {chan cmd} {
   set inRevision 0
   set inNode 0

   eval $cmd beginParsing
   
   while {![eof $chan]} {
      
      set line [gets $chan]
         
      if {[regexp {Revision-number: (.*)} $line -> revisionNumber]} {
         
         if {$inRevision} { eval $cmd endRevision }
         set inRevision 1
         eval $cmd beginRevision $revisionNumber
         ParseHeaders $chan $cmd
         ParseProperties $chan $cmd
         
      } elseif {[regexp {Node-path: (.*)} $line -> nodePath]} {
         
         eval $cmd [list beginNode $nodePath]
         set headers [ParseHeaders $chan $cmd]
         
         if {[dict exists $headers Prop-content-length]} {
            ParseProperties $chan $cmd
         }
         
         if {[dict exists $headers Text-content-length]} {
            set length [dict get $headers Text-content-length]
            eval $cmd beginTextContent
            ReadData $chan $length [list $cmd processTextContent]
         }
         
         eval $cmd endNode
         
      }
   }
   
   if {$inRevision} {eval $cmd endRevision }
   eval $cmd endParsing
   
   return
}

#
# Procedure for parsing Subversion properties
#
proc ::svndump::ParseProperties {chan cmd} {
   set line [gets $chan]
   while {[regexp {PROPS-END} $line] == 0} {
      
      if {[regexp {K (.*)} $line -> length]} {
         eval $cmd [list beginPropertyContent [read $chan $length]]
      } elseif {[regexp {V (.*)} $line -> length]} {
         ReadData $chan $length [list $cmd processPropertyContent]
         eval $cmd endPropertyContent
      }
      set line [gets $chan]
   }
   
   return
}

#
# Procedure for parsing DUMP file headers
#
proc ::svndump::ParseHeaders {chan cmd} {
   set line [gets $chan]
   
   set headerDict {}
   
   while {$line ne {}} {
      regexp {(.*): (.*)} $line -> header value
      dict set headerDict $header $value
      
      set line [gets $chan]
   }
   
   eval $cmd [list processHeaders $headerDict]
   
   return $headerDict
}

#
# Helper procedure to read data in chunks and call cmd to process said chunks.
#
proc ::svndump::ReadData {chan len cmd} {
   set readLen 0
   while {$readLen < $len} {
      set toRead $len
      if {$len > 10240} {
         set toRead 10240
      }
      
      eval $cmd [list [read $chan $toRead]]
      
      incr readLen $toRead
   }
   
   return
}